/** Cover art 'stuff'
 */

#include <stdio.h>
#include <jpeglib.h> 

#include "mcp.h"
#include "coverart.h"

// This is a quick & dirty helper class for basic jpg/png handling until a
// album art service client comes around.
// - or just pipe some image program for other image formats
//
// - start() begins the decompress step and sets width/height
//   - start() exists so that the stride value can be changed
//     (it defaults to the width)
// - finish() uses the same state initialized by the start() call
//   - data is malloc'd by finish()
//   - data is free'd in the deconstructor if it is not 0

// TODO  recombine start/finish and have the caller provide a function pointer
//       if needed to return a stride value

// NOTE  On second thought, don't spend much time fixing this up.

// -----------------------------------------------------------------------------
//  PNG                                                                   >fold>
#include <png.h>

#ifdef USE_PNG
class PngFormat : public CoverartFormat
    {
  public:
    png_structp png;
    png_infop png_info;
    png_infop png_end_info;

    long position;

    bool start(Coverart *ca);
    bool finish(Coverart *ca);

    PngFormat () { png = 0; png_info = 0; png_end_info = 0; position = 0; }
    ~PngFormat();
    };

PngFormat::~PngFormat ()
    {
    if ( png_end_info )
        png_destroy_read_struct(&png, &png_info, &png_end_info);
    else if ( png_info )
        png_destroy_read_struct(&png, &png_info, 0);
    else if ( png )
        png_destroy_read_struct(&png, 0, 0);
    }

static void png_read_data (png_structp png, png_bytep data_arg,
                           png_size_t length)
    {
    Coverart *ca = (Coverart *)png_get_io_ptr(png);
    PngFormat *format = (PngFormat *)ca->format;
    // TODO  should do .. something .. here if the length is somehow
    //       past the source length
    memcpy(data_arg, ca->source_data + format->position, length);
    format->position += length;
if ( format->position > ca->source_length )
 fprintf(stderr, "png: position exceeded len (%ld, %d)\n", format->position, ca->source_length);
    }

bool PngFormat::start (Coverart *ca)
    {
    png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
          // (png_voidp)png_error_ptr, png_error_fn, png_warning_fn);
    if ( !png ) return false;
    png_info = png_create_info_struct(png);
    if ( !png_info ) return false;
    png_end_info = png_create_info_struct(png);
    if ( !png_end_info ) return false;

    if ( setjmp(png_jmpbuf(png)) )
        return false;

    png_set_read_fn(png, ca, png_read_data);
    png_read_info(png, png_info);
    ca->width = png_get_image_width(png, png_info);
    ca->height = png_get_image_height(png, png_info);
    return true;
    }

bool PngFormat::finish (Coverart *ca)
    {
    png_byte bit_depth;
    png_byte color_type;
//    png_byte channels;

    if ( ca->stride == 0 )
        ca->stride = ca->width * 4;
    ca->allocData();

    if ( setjmp(png_jmpbuf(png)) )
        return false;

    bit_depth = png_get_bit_depth(png, png_info);
    color_type = png_get_color_type(png, png_info);
//    channels = png_get_channels(png, png_info);

    // not entirely sure how much is necessary here

    if ( bit_depth == 16 )
        png_set_strip_16(png);
    else if ( bit_depth < 8 )
        png_set_packing(png);

    if ( color_type == PNG_COLOR_TYPE_GRAY )
        png_set_filler(png, 0xff, PNG_FILLER_AFTER);
    if ( color_type == PNG_COLOR_TYPE_GRAY ||
         color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
        png_set_gray_to_rgb(png);

    if ( color_type == PNG_COLOR_TYPE_RGB )
        png_set_filler(png, 0xff, PNG_FILLER_AFTER);

#if __BYTE_ORDER == __BIG_ENDIAN
    // png format is rgba, cairo uses argb
    png_set_swap_alpha(png);
#else
    // on little endian, cairo wants bgra
    png_set_bgr(png);
#endif

    // png works with an array of pointers to each row
        {
        png_bytep rows[ca->height];
        for ( int i = 0; i < ca->height; i++ )
            rows[i] = (png_bytep)(ca->data + (i * ca->stride));
        png_read_image(png, rows);
        }

//    png_read_end(png, end_info);  // not currently using image info
    return true;
    }

#endif
// ---------------------------------------------------------------------- <fold<
//  JPEG                                                                  >fold>
#ifdef USE_JPEG

#include <jpeglib.h>
#include <setjmp.h>

struct local_jpeg_error
    {
    struct jpeg_error_mgr pub;
    jmp_buf stack;
    };

class JpegFormat : public CoverartFormat
    {
  public:
    struct jpeg_decompress_struct cinfo;
    struct local_jpeg_error err;
    struct jpeg_source_mgr source;

    bool start(Coverart *ca);
    bool finish(Coverart *ca);

    ~JpegFormat ()
        { jpeg_destroy_decompress(&cinfo); }
    };

static void jpeg_error_handler (j_common_ptr cinfo)
    {
    struct local_jpeg_error *p = (struct local_jpeg_error *)cinfo->err;
    longjmp(p->stack, 1);
    }

static void jpeg_dsm_init_source (j_decompress_ptr cinfo)
    {
    Coverart *ca = (Coverart *)cinfo->client_data;
    JpegFormat *o = (JpegFormat *)ca->format;

    o->source.next_input_byte = ca->source_data;
    o->source.bytes_in_buffer = ca->source_length;
    }

static boolean jpeg_dsm_fill_input_buffer (j_decompress_ptr cinfo)
    {
    static JOCTET fakeEOI[2] = { 0xFF, JPEG_EOI };
    Coverart *ca = (Coverart *)cinfo->client_data;
    JpegFormat *o = (JpegFormat *)ca->format;
    o->source.next_input_byte = fakeEOI;
    o->source.bytes_in_buffer = 2;
    return true;
    }

static void jpeg_dsm_skip_input_data (j_decompress_ptr cinfo, long num_bytes)
    {
    if ( num_bytes > 0 )
        {
        size_t silly_warning = num_bytes;
        Coverart *ca = (Coverart *)cinfo->client_data;
        JpegFormat *o = (JpegFormat *)ca->format;

        if ( silly_warning > o->source.bytes_in_buffer )
            silly_warning = o->source.bytes_in_buffer;
        o->source.next_input_byte += silly_warning;
        o->source.bytes_in_buffer -= silly_warning;
        }
    }

static boolean jpeg_dsm_resync_to_restart (j_decompress_ptr cinfo, int desired)
    {
    return jpeg_resync_to_restart(cinfo, desired);
//    return (*cinfo->resync_to_restart)(cinfo);
    }

static void jpeg_dsm_term_source (j_decompress_ptr cinfo)
    {
    // nothing needed
    }

bool JpegFormat::start (Coverart *ca)
    {
    cinfo.client_data = ca;
    cinfo.err = jpeg_std_error(&err.pub);
    err.pub.error_exit = jpeg_error_handler;

    if ( setjmp(err.stack) )
        {
fprintf(stderr, "jpegformat start() err\n");
        if ( mcp->verbose >= 5 )
            (*cinfo.err->output_message) ((jpeg_common_struct *)&cinfo);
        return false;
        }

    jpeg_create_decompress(&cinfo);

    source.init_source = jpeg_dsm_init_source;
    source.fill_input_buffer = jpeg_dsm_fill_input_buffer;
    source.skip_input_data = jpeg_dsm_skip_input_data;
    source.resync_to_restart = jpeg_dsm_resync_to_restart;
    source.term_source = jpeg_dsm_term_source;
    cinfo.src = &source;

    jpeg_read_header(&cinfo, TRUE);

    jpeg_start_decompress(&cinfo);

    if ( cinfo.output_components != 1 && cinfo.output_components != 3 )
        {
        mcp->log_verbose(1, "jpg: output_components not 1 or 3..\n");
        return 0;
        }

    ca->width = cinfo.output_width;
    ca->height = cinfo.output_height;

    return true;
    }

// FIXME  according to valgrind something is not getting freed that
//        jpeg_CreateDecompress allocates
bool JpegFormat::finish (Coverart *ca)
    {
    JSAMPARRAY buf;
    int i;
    unsigned char *p;

    if ( cinfo.output_components != 1 && cinfo.output_components != 3 )
        {
        free(ca->data);
        ca->data = 0;
        goto END_OF_LINE;
        }

    if ( ca->stride == 0 )
        ca->stride = cinfo.output_width * 4;
    ca->allocData();

    if ( setjmp(err.stack) )
        {
fprintf(stderr, "jpegformat finish() err\n");
        if ( mcp->verbose >= 5 )
            (*cinfo.err->output_message) ((jpeg_common_struct *)&cinfo);
        return false;
        }

    buf = (*cinfo.mem->alloc_sarray)
              ((j_common_ptr)&cinfo, JPOOL_IMAGE, ca->stride, 1);

    for ( i = 0; cinfo.output_scanline < cinfo.output_height; )
        {
        jpeg_read_scanlines(&cinfo, buf, 1);
        i = (cinfo.output_scanline-1) * ca->stride;
        p = (unsigned char *)buf[0];
        for ( int x = 0; x < ca->width; x++ )
            {
            int j = i + x*4;
#if __BYTE_ORDER == __BIG_ENDIAN
            if ( cinfo.output_components == 3 )
                {
                ca->data[j+1] = p[0];
                ca->data[j+2] = p[1];
                ca->data[j+3] = p[2];
                }
            else if ( cinfo.output_components == 1 )
                {
                ca->data[j+0] = p[0];
                ca->data[j+1] = p[0];
                ca->data[j+2] = p[0];
                }
            ca->data[j+0] = 255;
#else  // arbitrary default to little if definition is unset
            if ( cinfo.output_components == 3 )
                {
                ca->data[j+2] = p[0];
                ca->data[j+1] = p[1];
                ca->data[j+0] = p[2];
                }
            else if ( cinfo.output_components == 1 )
                {
                ca->data[j+0] = p[0];
                ca->data[j+1] = p[0];
                ca->data[j+2] = p[0];
                }
            ca->data[j+3] = 255;
#endif  // big endian
            p += cinfo.output_components;
            }
        }

END_OF_LINE:
    jpeg_finish_decompress(&cinfo);
    return ca->data != 0;
    }

#endif
// ---------------------------------------------------------------------- <fold<
//  Public functions                                                      >fold>

Coverart::Coverart (unsigned char *data_ptr, int len)
    {
    width = 0; height = 0; stride = 0;
    data = 0;
    format = 0;
    source_data = data_ptr;
    source_length = len;
    }

Coverart::~Coverart ()
    {
    if ( data )
        free(data);
    if ( format )
        delete format;
    }

void Coverart::allocData ()
    {
    if ( !(data = (unsigned char*)malloc(stride * height * 4)) )
        ;  // TODO  error handler..
    }

/** Returns true if the image format was recognized.
 * The only reason there is a start/finish instead of just one call is so
 * that the caller can change stride to a larger value for padding. (cairo)
 */
bool Coverart::start ()
    {
    if ( !source_data )
        return false;

#ifdef USE_JPEG
    JpegFormat *jpeg = new JpegFormat();
    format = jpeg;
    if ( jpeg->start(this) )
        return true;
    delete jpeg;
    format = 0;
#endif
#ifdef USE_PNG
    PngFormat *png = new PngFormat();
    format = png;
    if ( png->start(this) )
        return true;
    delete png;
    format = 0;
#endif

    return false;
    }

/** Returns true on success, the data field is malloc()'d and filled in the
 * cairo-usable rgba format.
 */
bool Coverart::finish ()
    {
    bool result = format->finish(this);
    delete format;
    format = 0;
    return result;
    }

// ---------------------------------------------------------------------- <fold<
